#include <tsin/display/GLDisplay.hpp>
#include <tsin/display/GLImage.hpp>
#include <tsin/display/DecoratedImage.hpp>

#include <tsin/config/Config.hpp>
#include <tsin/util/Logger.hpp>
#include <tsin/library/GameViewport.hpp>
#include <tsin/util/Resolution.hpp>

#ifdef WIN32
#include <windows.h>
#endif
#include <GL/gl.h>
#include <IL/il.h>
#include <IL/ilu.h>
#include <IL/ilut.h>
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>

#define _USE_MATH_DEFINES
#include <cmath>
#include <cstdlib>

#include <tsin/util/MemCheck.hpp>
using namespace tsin;

static ImageMap imageBase;

GLDisplay::GLDisplay()
        : config(0), viewport(0), icon(0)
{
        putenv("SDL_VIDEO_WINDOW_POS");
        putenv("SDL_VIDEO_CENTERED=1");
        SDL_Init(SDL_INIT_VIDEO);
}

GLDisplay::~GLDisplay()
{
        SDL_Quit();
        for (ImageMap::iterator it = imageBase.begin(), end = imageBase.end();
             it != end;
             it ++) {
                it->second->unref();
        }
}

bool GLDisplay::init(const Config &config)
{
        this->config = &config;

        SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
        SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5);
        SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
        SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 5);
        SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
        SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

        int flags = SDL_OPENGL | (config.fullscreen ? SDL_FULLSCREEN : 0);
        if (SDL_SetVideoMode(config.width, config.height,
                              config.bpp, flags) == 0) {
                return false;
        }
        //SDL_ShowCursor(SDL_DISABLE);

        if (!glSetup())
                return false;

        ilInit();
        iluInit();
        ilutRenderer(ILUT_OPENGL);

        resolution = config.resolution;
        xRight  = Resolution::internalRangeX(config.resolution) / 2;
        yTop    = Resolution::internalRangeY(config.resolution) / 2;
        xLeft   = - xRight;
        yBottom = - yTop;

        useOverallFrame();

        int tmp;
#define PRINT_ATTRIBUTE(name)  \
        SDL_GL_GetAttribute(name, &tmp); \
        tinfo << #name " = " << tmp << "\n";

        PRINT_ATTRIBUTE(SDL_GL_RED_SIZE)
        PRINT_ATTRIBUTE(SDL_GL_GREEN_SIZE)
        PRINT_ATTRIBUTE(SDL_GL_BLUE_SIZE)
        PRINT_ATTRIBUTE(SDL_GL_ALPHA_SIZE)
        PRINT_ATTRIBUTE(SDL_GL_DEPTH_SIZE)
        PRINT_ATTRIBUTE(SDL_GL_DOUBLEBUFFER)

#undef  PRINT_ATTRIBUTE

        return true;
}

bool GLDisplay::setWindowName(const char *name)
{
        SDL_WM_SetCaption(name, 0);
        return true;
}

bool GLDisplay::setWindowIcon(const char *name)
{
        if (icon != 0)
                SDL_FreeSurface(icon);

        icon = IMG_Load(name);
        if (icon == 0)
                return false;
        SDL_WM_SetIcon(icon, 0);
        return true;
}

bool GLDisplay::clear()
{
        glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        return true;
}

bool GLDisplay::update()
{
        SDL_GL_SwapBuffers();
        return true;
}

void GLDisplay::useOverallFrame()
{
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho(xLeft, xRight, yBottom, yTop, - zNear, - zFar);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();

        glViewport(0, 0, config->width, config->height);

        viewport = 0;
}

void GLDisplay::useViewportFrame(GameViewport &viewport)
{
        float hw = viewport.width() / 2, hh = viewport.height() / 2;
        int w = config->width, h = config->height;
        int rw = Resolution::resolutionX(resolution), rh = Resolution::resolutionY(resolution);

        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glFrustum(- hw, hw, - hh, hh, - zNear, - zFar);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();

        glViewport(Resolution::coord2pixelX(resolution, viewport.x()) * w / rw,
                   Resolution::coord2pixelY(resolution, viewport.y()) * h / rh,
                   Resolution::range2pixelX(resolution, viewport.width()) * w / rw,
                   Resolution::range2pixelY(resolution, viewport.height()) * h / rh);

        this->viewport = &viewport;
}

Image *GLDisplay::openImage(const char *path, StandardResolution res)
{
        GLImage *image = imageBase[path];
        if (image != 0) {
                image->ref();
                return image;
        }

        image = new GLImage();
        if (image == 0)
                return 0;
        image->open(path, res);
        imageBase[path] = image;
        image->ref();
        return image;
}

void GLDisplay::blitImage(Image &image, float x, float y, 
                          float width, float height, float angle, float z)
{
        setupBlendStyle(COVERING);
        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, image.getHandle());
        glColor3f(1.0f, 1.0f, 1.0f);
        glBegin(GL_QUADS);
        {
                glTexCoord2f(0, 0); glVertex3f(x        , y         , z);
                glTexCoord2f(1, 0); glVertex3f(x + width, y         , z);
                glTexCoord2f(1, 1); glVertex3f(x + width, y + height, z);
                glTexCoord2f(0, 1); glVertex3f(x        , y + height, z);
        }
        glEnd();
}

void GLDisplay::blitImage(DecoratedImage &image, float x, float y,
                          float width, float height, float angle, float z, int align)
{
        float initx, inity;
        if (align & HORIZONTAL_RIGHT)
                initx = -width/2;
        else if (align & HORIZONTAL_CENTER)
                initx = 0.0f;
        else
                initx = width/2;
        if (align & VERTICAL_TOP)
                inity = -height/2;
        else if (align & VERTICAL_CENTER)
                inity = 0.0f;
        else
                inity = height/2;

        setupBlendStyle(image.blendStyle());
        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, image.getHandle());

        glPushMatrix();
        glLoadIdentity();
        glTranslatef(x, y, z);
        glRotatef(angle / M_PI * 180, 0, 0, 1);
        glColor4f(image.r(), image.g(), image.b(), image.a());
        glBegin(GL_QUADS);
        {
                glTexCoord2f(image.left() , image.bottom()); glVertex3f(initx-width/2, inity-height/2, 0);
                glTexCoord2f(image.right(), image.bottom()); glVertex3f(initx+width/2, inity-height/2, 0);
                glTexCoord2f(image.right(), image.top()   ); glVertex3f(initx+width/2, inity+height/2, 0);
                glTexCoord2f(image.left() , image.top()   ); glVertex3f(initx-width/2, inity+height/2, 0);
        }
        glEnd();
        glPopMatrix();

        image.nextFrame();
}

bool GLDisplay::glSetup()
{
        glClearColor (0.0f, 0.0f, 0.0f, 1.0f);
        glClearDepth (0.0f);

        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

        return true;
}

void GLDisplay::setupBlendStyle(BlendStyle style)
{
        switch(style) {
        case COVERING:
                glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
                break;
        case OVERPRINTING:
                glBlendFunc(GL_SRC_ALPHA, GL_ONE);
                break;
        }
}
